Skip to content

feat(standards): add Role Based Access Control Account Component#2712

Open
onurinanc wants to merge 29 commits intonextfrom
role-based-access-control
Open

feat(standards): add Role Based Access Control Account Component#2712
onurinanc wants to merge 29 commits intonextfrom
role-based-access-control

Conversation

Copy link
Copy Markdown
Contributor

@PhilippGackstatter PhilippGackstatter left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Took an initial look, but haven't gotten through everything yet.

Comment on lines +970 to +972
proc get_admin_internal
exec.load_admin_info
# => [admin_suffix, admin_prefix, nominated_admin_suffix, nominated_admin_prefix]
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Should we rename load_admin_info to get_admin_config to consistently use the get prefix? And similarly save_admin_info -> set_admin_config? (Config since the slot is called config rather than info). Similarly for other load_ and save_ procedures.

#! - is_admin is 1 if the note sender is the current root admin, otherwise 0.
#!
#! Invocation: exec
proc is_sender_admin
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently, it's ambiguous whether "admin" refers to the root admin or a role admin. I'd make this unambiguous by using "root admin" when we mean that, e.g.:

  • is_sender_admin -> is_sender_root_admin
  • assert_sender_is_admin_or_role_admin -> assert_sender_is_root_or_role_admin
  • get_admin_internal -> get_root_admin_internal
  • ERR_SENDER_NOT_ADMIN -> ERR_SENDER_NOT_ROOT_ADMIN
  • ...

Comment on lines +34 to +36
# Per-role reverse member index map slot.
# Map entries: [0, role_symbol, account_suffix, account_prefix] -> [member_index_plus_one, 0, 0, 0]
const ROLE_MEMBER_INDEX_SLOT = word("miden::standards::access::role_based_access_control::role_member_index")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We use member_index_plus_one instead of member_index because we use 0 to indicate that a member does not have the role, right? I think it might be cleaner to use [is_member, member_index, 0, 0] instead and use the is_member boolean flag to check for presence instead. That way, we don't need to do arithmetic on the member_index.

The same may apply to active_role_index_plus_one, but I haven't checked yet.

Comment on lines +1374 to +1381
proc ensure_role_exists
dup eq.0
# => [is_zero, role_symbol]

if.true
drop
# => []
else
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From what I can tell, we never get here when the role symbol is zero, so can we not assume that the symbol is non-zero (and state this in the docs)? Alternatively, we can also call assert_role_symbol_non_zero, but that is technically redundant.

Comment on lines +351 to +352
pub proc role_exists
exec.is_role_exists
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Should we rename is_role_exists to role_exists_internal?

Comment on lines +80 to +82
# Default config for a newly created role.
# Layout: [member_count, admin_role_symbol, active_role_index_plus_one, exists_flag]
const DEFAULT_ROLE_CONFIG = [0, 0, 0, 1]
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question: Is there a meaningful difference between 1) a role exists and has 0 members and 2) the role does not exist? If not, I think we wouldn't need the exists_flag.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They are basically same, so, we can use member_count and remove the exists_flag.

Comment on lines +138 to +144
pub proc get_nominated_root_admin
exec.get_nominated_root_admin_internal
# => [nominated_root_admin_suffix, nominated_root_admin_prefix, pad(14)]

movup.2 drop movup.2 drop
# => [nominated_root_admin_suffix, nominated_root_admin_prefix, pad(14)]
end
Copy link
Copy Markdown
Contributor

@bobbinth bobbinth Apr 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question: could it make sense to use ownable2step component to manage the root admin? Or in other words, is there a reason why we can't or shouldn't do this? (if we can do this, it may simplify the code here a bit).

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ownable2Step and Role-Based AccessControl are two different abstractions. Ownership specifices "who owns this contract," while in role-based access control, the admin role specifies "who has the authority to manage roles." These represent two conceptually distinct responsibilities. Beyond that, both of these patterns have been conceptually tested and used for many years, coupling them together could introduce unexpected edge cases.

Furthermore, Ownable2Step and AccessControl can be used in the following ways:

  • A contract can use only Ownable2Step (for simple single-owner contracts)
  • A contract can use only Role-Based AccessControl (root admin + roles, without an owner concept)
  • A contract can use both independently (owner has upgrade authority, admin manages role permissions)

Regarding naming conventions, In the reference implementation, we use admin rather than root_admin. I think it's important to keep naming consistent with common patterns, this way, developers who have experience building on EVM and want to develop on Miden can adapt more quickly. For both this reason and to keep naming concise, I believe using admin instead of root_admin would be a better choice.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense. The main reason for the comment was that how we manage the admin looks very similar to how we manage the owner - and so, my thinking was that we could build RBAC on top of Ownable2step - i.e., RBAC component would depend on Ownalbe2step - similar to how some other components do (e.g., fungible faucets).

So, to clarify - I didn't want to merge them. We'd still have two components:

  • Ownable2Step - same as now.
  • Role-Based AccessControl - which would require Ownable2Step to be present and would basically treat owner as the admin.

But if that complicates things - I think we can keep things as they are.

A contract can use both independently (owner has upgrade authority, admin manages role permissions)

Wouldn't this be a sort of an anti-pattern? Basically, if an account uses RBAC, why not create a role that can handle upgrades (e.g., UPGRADER)? I think having some roles managed by RBAC and others managed by Ownable2Step would actually be somewhat confusing and could also introduce unexpected behavior.

This was another motivation for suggesting that RBAC builds on Ownable2Step - to avoid such potential complications.

Comment on lines +57 to +61
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RoleBasedAccessControl {
root_admin: AccountId,
roles: BTreeMap<RoleSymbol, RoleInit>,
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we provide a comprehensive description somewhere about how the RBAC system works? I think we have most of these in various GH comments - but would be great to have it in the code comments as well.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We'll have a dedicated page for Miden similar to this, and I believe this would be more expressive in general: https://docs.openzeppelin.com/contracts/3.x/access-control

Would you prefer to have the dedicated page sooner and give a reference to that page in the comments? Or, would you include a comment for now, then we can add a reference link to the dedicated page? Another idea is to have both comments and the dedicated page?

We would also like to do that for the Token Standards, I think it might be good to prioritize the OZ documentation page?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think eventually we should have both the inline docs and the page. The inline docs don't need to be as comprehensive - they just need to describe the general concepts that would make understanding the code a bit easier. So, I'd add the inline docs now and then would add a more comprehensive page later.

@onurinanc onurinanc requested a review from bobbinth April 21, 2026 11:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants